
package com.huaweicloud.haydn.delivercore.agent.service.metadata.impl;

import static com.huaweicloud.haydn.delivercore.agent.config.Constants.FILETYPE_ZIP;
import static com.huaweicloud.haydn.delivercore.agent.config.Constants.SYS_URL;
import static com.huaweicloud.haydn.delivercore.agent.config.Constants.TYPE_DB;
import static com.huaweicloud.haydn.delivercore.agent.config.Constants.TYPE_EMPTY;
import static com.huaweicloud.haydn.delivercore.agent.config.Constants.TYPE_FIELD;
import static com.huaweicloud.haydn.delivercore.agent.config.Constants.TYPE_TABLE;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.read.listener.ReadListener;
import com.alibaba.excel.support.ExcelTypeEnum;
import com.alibaba.fastjson.JSON;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.huaweicloud.haydn.delivercore.agent.common.exception.BusinessErrorEnum;
import com.huaweicloud.haydn.delivercore.agent.common.exception.BusinessException;
import com.huaweicloud.haydn.delivercore.agent.dto.CommonResult;
import com.huaweicloud.haydn.delivercore.agent.dto.excel.DbExcelDto;
import com.huaweicloud.haydn.delivercore.agent.dto.excel.FieldEmptyExcelDto;
import com.huaweicloud.haydn.delivercore.agent.dto.excel.FieldExcelDto;
import com.huaweicloud.haydn.delivercore.agent.dto.excel.TableExcelDto;
import com.huaweicloud.haydn.delivercore.agent.dto.haydn.DataReportDto;
import com.huaweicloud.haydn.delivercore.agent.dto.haydn.DbDto;
import com.huaweicloud.haydn.delivercore.agent.dto.haydn.ExcelParseDto;
import com.huaweicloud.haydn.delivercore.agent.dto.haydn.ExcelParseGroupDto;
import com.huaweicloud.haydn.delivercore.agent.dto.haydn.FieldDto;
import com.huaweicloud.haydn.delivercore.agent.dto.haydn.FieldEmptyDto;
import com.huaweicloud.haydn.delivercore.agent.dto.haydn.TableDto;
import com.huaweicloud.haydn.delivercore.agent.service.HttpRequestService;
import com.huaweicloud.haydn.delivercore.agent.service.metadata.DataExplorationService;

import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

import org.apache.commons.collections4.CollectionUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

/**
 * @since 2022-12-13
 */
@Service
@Slf4j
public class DataExplorationServiceImpl implements DataExplorationService {

    private static final String[] METADATA_TYPE = {TYPE_DB, TYPE_TABLE, TYPE_FIELD, TYPE_EMPTY};

    private static final List<String> METADATA_TYPE_LIST = Arrays.asList(METADATA_TYPE);

    private static final String NUMBER_REGEXP = "^[0-9]{8}$";

    @Autowired
    private HttpRequestService httpRequestService;

    @Override
    @SneakyThrows
    public void dataUpload() {
        log.info("here");

    }

    @Override
    public void parseAndUpload(String accessCode, String projectId, String url) {
        File file = null;
        ZipFile zip = null;
        HttpEntity<String> entity = null;
        try {
            file = new File(System.getProperty(SYS_URL), "result." + FILETYPE_ZIP);
            zip = new ZipFile(file, StandardCharsets.UTF_8);
            Map<String, ExcelParseGroupDto> excelParseGroupMap = new HashMap<>();
            Enumeration<? extends ZipEntry> entries = zip.entries();
            while (entries.hasMoreElements()) {
                parseZip(zip, entries, excelParseGroupMap);
            }
            List<DataReportDto> dataReportDtos = new ArrayList<>();
            excelParseGroupMap.forEach((k, parseGroup) -> {
                DataReportDto dataReportDto = parseGroupedExcelParseDtos(parseGroup.getDbExcelMap(),
                    parseGroup.getTableExcelMap(), parseGroup.getFieldExcelMap(), parseGroup.getFieldEmptyExcelMap());
                dataReportDtos.add(dataReportDto);
            });
            HttpHeaders headers = new HttpHeaders();
            headers.set("Content-Type", "application/json;charset=utf8");
            headers.set("access-code", accessCode);
            headers.set("deliver-project-id", projectId);
            entity = new HttpEntity<>(new ObjectMapper().writeValueAsString(dataReportDtos), headers);
            ResponseEntity<CommonResult> resp =
                httpRequestService.execute(url, HttpMethod.POST, entity, CommonResult.class);

            log.info("dto: \n" + JSON.toJSONString(dataReportDtos));
            log.info("entity: \n" + entity);
            log.info("body: \n" + new ObjectMapper().writeValueAsString(dataReportDtos));

            log.info(resp.toString());
            if (resp.getBody() == null) {
                log.error("createSpecs return null");
                throw new BusinessException(BusinessErrorEnum.BASE_INTERNAL_SERVER_ERROR);
            }

        } catch (IOException | ParseException e) {
            log.error(e.getMessage());
        } finally {
            if (zip != null) {
                try {
                    zip.close();
                } catch (IOException e) {
                    log.error("Close zip failed!");
                    log.error(e.getMessage(), e);
                }
            }
        }

    }

    private void parseZip(ZipFile zip, Enumeration<? extends ZipEntry> entries,
        Map<String, ExcelParseGroupDto> excelParseGroupMap) throws ParseException, IOException {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
        ZipEntry zipEntry = entries.nextElement();
        String csvFullName = zipEntry.getName();
        String csvName = csvFullName.substring(0, csvFullName.lastIndexOf(".")).toLowerCase(Locale.US);
        csvNameCheck(csvName);
        String[] split = csvName.split("_");
        String prefix = split[0];
        String typeName = split[1];
        String dataName = split[2];
        if (!"csv".equals(csvFullName.substring(csvFullName.lastIndexOf(".") + 1).toLowerCase(Locale.US))) {
            throw new BusinessException(BusinessErrorEnum.BASE_INTERNAL_SERVER_ERROR);
        }
        ExcelParseDto excelParseDto = new ExcelParseDto();
        excelParseDto.setName(csvFullName);
        excelParseDto.setGenDate(sdf.parse(dataName));
        excelParseDto.setZipFile(zip);
        excelParseDto.setZipEntry(zipEntry);
        ExcelParseGroupDto parseGroup = excelParseGroupMap.getOrDefault(prefix, new ExcelParseGroupDto());
        if (TYPE_DB.equals(typeName)) {
            parseGroup.getDbExcelMap().add(excelParseDto);
        } else if (TYPE_TABLE.equals(typeName)) {
            parseGroup.getTableExcelMap().add(excelParseDto);
        } else if (TYPE_FIELD.equals(typeName)) {
            parseGroup.getFieldExcelMap().add(excelParseDto);
        } else {
            parseGroup.getFieldEmptyExcelMap().add(excelParseDto);
        }
        excelParseGroupMap.put(prefix, parseGroup);

    }

    private DataReportDto parseGroupedExcelParseDtos(List<ExcelParseDto> dbExcelList,
        List<ExcelParseDto> tableExcelList, List<ExcelParseDto> fieldExcelList,
        List<ExcelParseDto> fieldEmptyExcelList) {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
        Collections.sort(dbExcelList);
        Collections.sort(tableExcelList);
        Collections.sort(fieldExcelList);
        Collections.sort(fieldEmptyExcelList);
        Map<String, List<DbDto>> dateDbsMap = new HashMap<>();
        Map<String, List<TableDto>> dateTablesMap = new LinkedHashMap<>();
        Map<String, List<FieldDto>> dateFieldsMap = new HashMap<>();
        if (CollectionUtils.isNotEmpty(dbExcelList)) {
            for (ExcelParseDto dto : dbExcelList) {
                List<DbDto> dbDtos = genDbDtoList(dto.getZipFile(), dto.getZipEntry());
                dateDbsMap.put(sdf.format(dto.getGenDate()), dbDtos);
            }
        }
        if (CollectionUtils.isNotEmpty(tableExcelList)) {
            tableExcelList.forEach(dto -> dateTablesMap.put(sdf.format(dto.getGenDate()),
                genTableDtoList(dto.getZipFile(), dto.getZipEntry())));
        }
        if (CollectionUtils.isNotEmpty(fieldExcelList)) {
            fieldExcelList.forEach(dto -> dateFieldsMap.put(sdf.format(dto.getGenDate()),
                genFieldDtoList(dto.getZipFile(), dto.getZipEntry())));
            if (CollectionUtils.isNotEmpty(fieldEmptyExcelList)) {
                List<FieldEmptyDto> fieldEmptyDtos = new ArrayList<>();
                fieldEmptyExcelList.forEach(fieldEmptyExcelParseDto -> fieldEmptyDtos
                    .addAll(genFieldEmptyDtoList(fieldEmptyExcelParseDto.getStream())));
                Map<String, String> fieldEmptyMap = new HashMap<>();
                fieldEmptyDtos.forEach(dto -> fieldEmptyMap.put(
                    Optional.ofNullable(dto.getDatalinkName()).orElse("")
                        + Optional.ofNullable(dto.getDatabaseName()).orElse("")
                        + Optional.ofNullable(dto.getSchemaName()).orElse("")
                        + Optional.ofNullable(dto.getTableName()).orElse("")
                        + Optional.ofNullable(dto.getFieldNameEn()).orElse(""),
                    Optional.ofNullable(dto.getEmptyRate()).orElse("")));
                dateFieldsMap.forEach((k, fieldDtos) -> {
                    fieldDtos.forEach(fieldDto -> fieldDto.setEmptyRate(fieldEmptyMap
                        .getOrDefault(fieldDto.getDatalinkName() + fieldDto.getDatabaseName() + fieldDto.getSchemaName()
                            + fieldDto.getDeliverReqmtTableName() + fieldDto.getFieldNameEn(), "")));
                });
            }
        }
        DataReportDto dataReportDto = new DataReportDto();
        dataReportDto.setDateDbsMap(dateDbsMap);
        dataReportDto.setDateTablesMap(dateTablesMap);
        dataReportDto.setDateFieldsMap(dateFieldsMap);
        return dataReportDto;
    }

    private void csvNameCheck(String csvName) {
        if (!csvName.contains("_")) {
            throw new BusinessException(BusinessErrorEnum.BASE_INTERNAL_SERVER_ERROR);
        }
        String[] split = csvName.split("_");
        if (split.length > 3 || !METADATA_TYPE_LIST.contains(split[1]) || !split[2].matches(NUMBER_REGEXP)) {
            throw new BusinessException(BusinessErrorEnum.BASE_INTERNAL_SERVER_ERROR);
        }
    }

    private List<DbDto> genDbDtoList(ZipFile zipFile, ZipEntry zipEntry) {
        List<DbDto> resList = new ArrayList<>();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
        try (InputStream stream = zipFile.getInputStream(zipEntry)) {
            EasyExcel.read(stream, DbExcelDto.class, new ReadListener<DbExcelDto>() {
                @Override
                public void invoke(DbExcelDto data, AnalysisContext analysisContext) {
                    DbDto dbDto = new DbDto();
                    BeanUtils.copyProperties(data, dbDto);
                    try {
                        dbDto.setDataGenTime(sdf.parse(data.getGenTime()));
                    } catch (ParseException e) {
                        log.error(e.getMessage());
                        throw new BusinessException(BusinessErrorEnum.BASE_INTERNAL_SERVER_ERROR);
                    }
                    resList.add(dbDto);
                }

                @Override
                public void doAfterAllAnalysed(AnalysisContext analysisContext) {
                }
            }).excelType(ExcelTypeEnum.CSV).autoTrim(true).headRowNumber(1).sheet().doRead();
            if (resList.size() != 1) {
                return null;
            }
            return resList;
        } catch (IOException e) {
            log.error(e.getMessage());
            throw new BusinessException(BusinessErrorEnum.BASE_INTERNAL_SERVER_ERROR);
        }
    }

    private List<TableDto> genTableDtoList(ZipFile zipFile, ZipEntry zipEntry) {
        List<TableDto> resList = new ArrayList<>();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
        try (InputStream stream = zipFile.getInputStream(zipEntry)) {
            EasyExcel.read(stream, TableExcelDto.class, new ReadListener<TableExcelDto>() {
                @Override
                public void invoke(TableExcelDto data, AnalysisContext analysisContext) {
                    TableDto tableDto = new TableDto();
                    BeanUtils.copyProperties(data, tableDto);
                    tableDto.setIsDataLive("0");
                    String nonNullSchema = Optional.ofNullable(tableDto.getSchemaName()).orElse("");
                    tableDto.setSchemaName(nonNullSchema);
                    try {
                        tableDto.setDataGenTime(sdf.parse(data.getGenTime()));
                    } catch (ParseException e) {
                        log.error(e.getMessage());
                        throw new BusinessException(BusinessErrorEnum.BASE_INTERNAL_SERVER_ERROR);
                    }
                    resList.add(tableDto);
                }

                @Override
                public void doAfterAllAnalysed(AnalysisContext analysisContext) {
                }
            }).excelType(ExcelTypeEnum.CSV).autoTrim(true).headRowNumber(1).sheet().doRead();
            return resList;
        } catch (IOException e) {
            log.error(e.getMessage());
            throw new BusinessException(BusinessErrorEnum.BASE_INTERNAL_SERVER_ERROR);
        }
    }

    private List<FieldDto> genFieldDtoList(ZipFile zipFile, ZipEntry zipEntry) {
        List<FieldDto> resList = new ArrayList<>();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
        try (InputStream stream = zipFile.getInputStream(zipEntry)) {
            EasyExcel.read(stream, FieldExcelDto.class, new ReadListener<FieldExcelDto>() {
                @Override
                public void invoke(FieldExcelDto data, AnalysisContext analysisContext) {
                    FieldDto fieldDto = new FieldDto();
                    BeanUtils.copyProperties(data, fieldDto);
                    String nonNullSchema = Optional.ofNullable(fieldDto.getSchemaName()).orElse("");
                    fieldDto.setSchemaName(nonNullSchema);
                    try {
                        fieldDto.setDataGenTime(sdf.parse(data.getGenTime()));
                        fieldDto.setDeliverReqmtTableName(data.getTableName());
                    } catch (ParseException e) {
                        log.error(e.getMessage());
                        throw new BusinessException(BusinessErrorEnum.BASE_INTERNAL_SERVER_ERROR);
                    }
                    resList.add(fieldDto);
                }

                @Override
                public void doAfterAllAnalysed(AnalysisContext analysisContext) {
                }
            }).excelType(ExcelTypeEnum.CSV).autoTrim(true).headRowNumber(1).sheet().doRead();
            return resList;
        } catch (IOException e) {
            log.error(e.getMessage());
            throw new BusinessException(BusinessErrorEnum.BASE_INTERNAL_SERVER_ERROR);
        }
    }

    private List<FieldEmptyDto> genFieldEmptyDtoList(InputStream stream) {
        List<FieldEmptyDto> resList = new ArrayList<>();
        EasyExcel.read(stream, FieldEmptyExcelDto.class, new ReadListener<FieldEmptyExcelDto>() {
            @Override
            public void invoke(FieldEmptyExcelDto data, AnalysisContext analysisContext) {
                FieldEmptyDto fieldEmptyDto = new FieldEmptyDto();
                BeanUtils.copyProperties(data, fieldEmptyDto);
                String nonNullSchema = Optional.ofNullable(fieldEmptyDto.getSchemaName()).orElse("");
                fieldEmptyDto.setSchemaName(nonNullSchema);
                resList.add(fieldEmptyDto);
            }

            @Override
            public void doAfterAllAnalysed(AnalysisContext analysisContext) {
            }
        }).excelType(ExcelTypeEnum.CSV).autoTrim(true).headRowNumber(1).sheet().doRead();
        return resList;
    }
}
